package ua.kpi.ipsa.expert.api.net;

import java.io.*;
import java.rmi.*;
import java.util.*;

import ua.kpi.ipsa.expert.api.algorithm.*;
import ua.kpi.ipsa.expert.api.db.*;

/**
 * 
 * @author doctorpro
 * 
 *         GodConsole is a kind of communist console believing that all users
 *         have similar rights on the system
 * 
 */
public final class DefaultUsersConsole implements Serializable, UsersConsole {

	//
	private final UserListener userListener;

	//
	private final String currentUserLogin;

	//
	private volatile boolean enabled = true;

	//
	private ServerToUserLeash usersLeash;

	//
	private UserConsoleManager userManager;

	/**
	 * 
	 */
	public String getLogin() {
		return currentUserLogin;
	}

	/**
	 * 
	 * @param userListener
	 */
	public DefaultUsersConsole(UserConsoleManager userManager,
			UserListener userListener, String currentUserLogin,
			ServerToUserLeash usersLeash) {
		this.userManager = userManager;
		this.userListener = userListener;
		this.usersLeash = usersLeash;
		this.currentUserLogin = currentUserLogin;
	}

	/**
	 * 
	 */
	public boolean assertUserOnline() throws RemoteException {
		return usersLeash.assertOnline();
	}

	/**
	 * 
	 * @param enabled
	 * @return
	 */
	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
		try {
			usersLeash.setDisconnected();
		} catch (RemoteException e) {
			// TODO e.printStackTrace();
			e.printStackTrace();
		}
	}

	/**
	 * 
	 */
	@Override
	public boolean isEnabled() throws RemoteException {
		return enabled;
	}

	/**
	 * 
	 */
	@Override
	public void postClassFile(byte[] bytes, String fullClassName)
			throws RemoteException, ConsoleException {
		if (!enabled) {
			throw new ConsoleException("Console is inactive");
		}
		userListener.userAction(currentUserLogin, "postClassFile",
				UserListener.MARKER_START, null);

		;

		userListener.userAction(currentUserLogin, "postClassFile",
				UserListener.MARKER_END_NOINFO, null);
	}

	/**
	 * 
	 */
	@Override
	public int postCodePortion(String name, String group, String vooz,
			String etc, List<SourceFile> sourceList) throws RemoteException,
			ConsoleException {
		userListener.userAction(currentUserLogin, "postCodePortion",
				UserListener.MARKER_START, null);

		/*
		 * Added synchronization because we need the code portions to be added
		 * with increasing ids and times. And we want codes and portions to
		 * appear simultaneously. We use this in CodePresentationManager class
		 * too.
		 */
		synchronized (userManager.dao.getCodePortionLock()) {
			CodePortionTuple cpt = new CodePortionTuple();
			cpt.etc = etc;
			cpt.studentName = name;
			cpt.userLogin = currentUserLogin;
			cpt.group = group;
			cpt.vooz = vooz;
			cpt.time = System.currentTimeMillis();

			try {
				System.out.println("server: setting code portion");
				// set the code portion
				userManager.dao.setCodePortion(cpt);
				// set the codes
				int cpId = cpt.codePortionPK;
				for (Iterator<SourceFile> i = sourceList.iterator(); i
						.hasNext();) {
					SourceFile sf = i.next();
					CodeTuple ct = new CodeTuple();
					ct.codePK = 0;
					ct.codePortionTupleFK = cpId;
					ct.fileName = sf.path;
					ct.text = sf.body;
					userManager.dao.setCode(ct);
				}
			} catch (DataAccessException e) {
				e.printStackTrace();
				throw new RemoteException("Data access exception", e);
			}
		}
		userManager.presentationManager.update();

		userListener.userAction(currentUserLogin, "postCodePortion",
				UserListener.MARKER_END_NOINFO, null);
		return 0;
	}

	public String arrayToString(String[] s) {

		String result = "";

		for (int i = 0; i < s.length; i++) {
			result += s[i];
		}

		return result;
	}

	/**
	 * 
	 */
	@Override
	public List<Integer> getAppointedAlgorithmSet(int portionId)
			throws RemoteException, ConsoleException {
		userListener.userAction(currentUserLogin, "getAppointedAlgorithmSet",
				UserListener.MARKER_START, null);

		userListener.userAction(currentUserLogin, "getAppointedAlgorithmSet",
				UserListener.MARKER_END_NOINFO, null);
		return null;
	}

	/**
	 * 
	 */
	@Override
	public void postAlgorithmAppointment(int portionId, int algorithmId)
			throws RemoteException, ConsoleException {
		userListener.userAction(currentUserLogin, "postAlgorithmAppointment",
				UserListener.MARKER_START, null);

		try {
			userManager.algoRunManager.addAlgorithm(algorithmId, portionId);
		} catch (DataAccessException e) {
			e.printStackTrace();
			throw new RemoteException("DataAccessException", e);
		}

		userListener.userAction(currentUserLogin, "postAlgorithmAppointment",
				UserListener.MARKER_END_NOINFO, null);
	}

	/**
	 * 
	 */
	@Override
	public String getAlgorithmPrintableName(int algorithmId)
			throws RemoteException, ConsoleException {
		userListener.userAction(currentUserLogin, "getAlgorithmPrintableName",
				UserListener.MARKER_START, null);

		userListener.userAction(currentUserLogin, "getAlgorithmPrintableName",
				UserListener.MARKER_END_NOINFO, null);
		return null;
	}

	/**
	 * 
	 */
	@Override
	public void postNewAlgorithm(String fullClassName) throws RemoteException,
			ConsoleException {
		userListener.userAction(currentUserLogin, "postNewAlgorithm",
				UserListener.MARKER_START, null);

		AlgorithmTuple aat = new AlgorithmTuple();
		aat.className = fullClassName;
		try {
			userManager.dao.setAlgorithm(aat);
		} catch (DataAccessException e) {
			e.printStackTrace();
			throw new RemoteException("DataAccessException", e);
		}

		userListener.userAction(currentUserLogin, "postNewAlgorithm",
				UserListener.MARKER_END_NOINFO, null);
	}

	/**
	 * 
	 */
	@Override
	public void removeAlgorithm(int algorithmId) throws RemoteException,
			ConsoleException {
		userListener.userAction(currentUserLogin, "removeAlgorithm",
				UserListener.MARKER_START, null);

		;

		userListener.userAction(currentUserLogin, "removeAlgorithm",
				UserListener.MARKER_END_NOINFO, null);
	}

	/**
	 * 
	 */
	@Override
	public void releaseConsole() throws RemoteException, ConsoleException {
		userListener.userAction(currentUserLogin, "releaseConsole",
				UserListener.MARKER_START, null);

		userManager.releaseConsole(this);

		userListener.userAction(currentUserLogin, "releaseConsole",
				UserListener.MARKER_END_NOINFO, null);
	}

	@Override
	public List<CPSummaryData> getCodePortionSummaryData(int id, String name,
			String group, String vooz, String etc) throws RemoteException {

		// stub
		List<CPSummaryData> l = new ArrayList<CPSummaryData>();
		if ((name != null) && name.equals("")) {
			name = null;
		}
		if ((group != null) && group.equals("")) {
			group = null;
		}
		if ((vooz != null) && vooz.equals("")) {
			vooz = null;
		}
		if ((etc != null) && etc.equals("")) {
			etc = null;
		}

		CodePortionTuple ex = new CodePortionTuple();
		ex.codePortionPK = id;
		ex.studentName = name;
		ex.etc = etc;
		ex.group = group;
		ex.vooz = vooz;
		ex.time = 0;

		try {
			List<CodePortionTuple> cpts = userManager.dao
					.getCodePortionsByExample(ex, Long.MAX_VALUE);
			for (Iterator<CodePortionTuple> i = cpts.iterator(); i.hasNext();) {
				CodePortionTuple cpt = i.next();
				CPSummaryData cpsd = new CPSummaryData();
				cpsd.percent = 1;
				cpsd.time = cpt.time;
				cpsd.studName = cpt.studentName;
				cpsd.id = cpt.codePortionPK;
				cpsd.vooz = cpt.vooz;
				cpsd.group = cpt.group;
				cpsd.loaderName = cpt.userLogin;
				l.add(cpsd);
			}
		} catch (DataAccessException e) {
			e.printStackTrace();
			throw new RemoteException("DataAccessException", e);
		}

		return l;
	}

	@Override
	public List<Algorithm> getAvaivableAlgorithms(int codePortionId)
			throws RemoteException {
		try {
			List<Algorithm> list = new ArrayList<Algorithm>();
			Set<Integer> used = new HashSet<Integer>();
			for (AssignedAlgorithmTuple ass : userManager.dao
					.getAssignedAlgorithms(codePortionId))
				used.add(ass.algorithmPFK);
			for (AlgorithmTuple a : userManager.dao.getAlgorithms())
				if (!used.contains(a.algorithmPK)) {

					// TODO: temp
					try {
						a.name = userManager.algoRunManager
								.getAlgoName(a.algorithmPK);
					} catch (DataAccessException ex) {
						// Чтобы ошибка в добавлении имени не убивала весь
						// список алгоритмов
					}
					// end of temp

					list.add(new Algorithm(
							a.algorithmPK,
							a.name,
							countAlgoTime(a, codePortionId),
							a.userOpinionMeasurementCount > 0 ? a.userOpinionPositiveCount
									* 1.0 / a.userOpinionMeasurementCount
									: -1));
				}
			return list;
		} catch (DataAccessException e) {
			e.printStackTrace();
			throw new RemoteException("DataAccessException", e);
		}
	}

	/**
	 * 
	 * @param at
	 * @param cpId
	 * @return
	 */
	private long countAlgoTime(AlgorithmTuple at, int cpId) {
		return (long) (600000 * Math.random() / Math.random());
	}

	@Override
	public List<CPAlgoSummaryData> getCPAlgoSummaryData(int codePortionId)
			throws RemoteException {

		ArrayList<CPAlgoSummaryData> l = new ArrayList<CPAlgoSummaryData>();

		try {
			List<AssignedAlgorithmTuple> algoIds = userManager.dao
					.getAssignedAlgorithms(codePortionId);

			Set<Integer> algos = getAlgorithmsWithHypos(codePortionId);
			for (Iterator<AssignedAlgorithmTuple> i = algoIds.iterator(); i
					.hasNext();) {
				CPAlgoSummaryData cpasd = new CPAlgoSummaryData();
				int nextAlgoId = i.next().algorithmPFK;

				AlgorithmTuple at = userManager.dao.getAlgorithm(nextAlgoId);
				// TODO: temp
				try {
					at.name = userManager.algoRunManager
							.getAlgoName(at.algorithmPK);
				} catch (DataAccessException ex) {
					// Чтобы ошибка в добавлении имени не убивала весь список
					// алгоритмов
				}
				// end of temp
				cpasd.name = at.name;
				cpasd.percentage = getAlgoPercentage(codePortionId, nextAlgoId);
				cpasd.hypoFound = algos.contains(nextAlgoId);

				l.add(cpasd);
			}
		} catch (DataAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// // stub
		// CPAlgoSummaryData cpasd1 = new CPAlgoSummaryData(), cpasd2 = new
		// CPAlgoSummaryData(), cpasd3 = new CPAlgoSummaryData();
		// cpasd1.hypoFound = true;
		// cpasd2.hypoFound = true;
		// cpasd3.hypoFound = false;
		// cpasd1.name = "метод приближений Карягина";
		// cpasd2.name = "алгоритм равномерной композиции";
		// cpasd3.name = "разлостный метод Крохельберга";
		// cpasd1.percentage = 0.1;
		// cpasd2.percentage = 0.2;
		// cpasd3.percentage = 0.314;
		// l.add(cpasd1);
		// l.add(cpasd2);
		// l.add(cpasd3);

		return l;
	}

	/**
	 * 
	 * @param codePortionId
	 * @param algoId
	 * @return
	 */
	private Set<Integer> getAlgorithmsWithHypos(int codePortionId)
			throws DataAccessException {

		Set<Integer> res = new HashSet<Integer>();

		List<CodeTuple> codes = userManager.dao
				.getCodeByPortionKey(codePortionId);

		for (CodeTuple code : codes)
			for (PlagiarismHypoTuple hypo : userManager.dao
					.getPlagiarismHyposByCode(code.codePK))
				res.add(hypo.algorithmFK);

		return res;
	}

	/**
	 * Stub: should use some algorithm progress supervisor to count real progrez
	 * 
	 * @return
	 */
	private double getAlgoPercentage(int codePortionId, int algoId)
			throws IllegalArgumentException {

		return 0.34 + Math.random() / 10;
	}

	/**
	 * 
	 */
	@Override
	public CodePortion getCodePortion(int codePortionPK)
			throws RemoteException, ConsoleException {
		try {
			List<SourceFile> codes = new ArrayList<SourceFile>();
			for (CodeTuple c : userManager.dao
					.getCodeByPortionKey(codePortionPK))
				codes.add(new SourceFile(c.fileName, c.text));
			CodePortionTuple cp = userManager.dao.getCodePortionsByExample(
					new CodePortionTuple(codePortionPK), Long.MAX_VALUE).get(0);
			return new CodePortion(cp.userLogin, cp.studentName, cp.group,
					cp.vooz, cp.etc, codes);
		} catch (DataAccessException e) {
			e.printStackTrace();
			throw new RemoteException("DataAccessException", e);
		}
	}

	/**
	 * 
	 */
	@Override
	public void postUserAgreement(int hypoPK, int userOpinion)
			throws RemoteException, ConsoleException {
		try {
			PlagiarismHypoTuple hypo = userManager.dao
					.getPlagiarismHypo(hypoPK);
			hypo.userOpinion = userOpinion;
			userManager.dao.setPlagiarismHypo(hypo);
		} catch (DataAccessException e) {
			e.printStackTrace();
			throw new RemoteException("DataAccessException", e);
		}
	}

	private class SourcePair {
		public SourceFile s;
		public int codePortion;

		public SourcePair(SourceFile s, int codePortion) {
			this.s = s;
			this.codePortion = codePortion;
		}
	}

	/**
	 * 
	 */
	@Override
	public List<PlagiarismHypo> getPortionScore(int portionId, int algorithmId)
			throws RemoteException, ConsoleException {
		try {
			Map<Integer, SourcePair> src = new HashMap<Integer, SourcePair>();
			List<PlagiarismHypo> list = new ArrayList<PlagiarismHypo>();
			for (CodeTuple code : userManager.dao
					.getCodeByPortionKey(portionId)) {
				for (PlagiarismHypoTuple hypo : userManager.dao
						.getPlagiarismHyposByCode(code.codePK)) {
					list.add(new PlagiarismHypo(hypo.plagiarismHypoPK,
							hypo.veracity, loadSource(src, code.codePK).s,
							loadSource(src, hypo.code2FK).codePortion,
							loadSource(src, hypo.code2FK).s,
							hypo.code1PlagStart, hypo.code1PlagEnd,
							hypo.code2PlagStart, hypo.code2PlagEnd,
							hypo.userOpinion));
				}
			}
			return list;
		} catch (DataAccessException e) {
			e.printStackTrace();
			throw new RemoteException("DataAccessException", e);
		}
	}

	private SourcePair loadSource(Map<Integer, SourcePair> src, int code)
			throws DataAccessException {
		SourcePair p = src.get(code);
		if (p != null)
			return p;
		CodeTuple c = userManager.dao.getCode(code);
		p = new SourcePair(new SourceFile(c.fileName, c.text),
				c.codePortionTupleFK);
		src.put(code, p);
		return p;
	}
}
